package com.wanghua.mall.product.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.wanghua.mall.product.frontvo.Catalog2Vo;
import com.wanghua.mall.product.service.BrandCategoryRelationService;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.wanghua.common.utils.PageUtils;
import com.wanghua.common.utils.Query;

import com.wanghua.mall.product.dao.CategoryDao;
import com.wanghua.mall.product.entity.CategoryEntity;
import com.wanghua.mall.product.service.CategoryService;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;


@Service
public class CategoryServiceImpl extends ServiceImpl<CategoryDao, CategoryEntity> implements CategoryService {
    @Resource
    private BrandCategoryRelationService brandCategoryRelationService;
    @Resource
    private StringRedisTemplate stringRedisTemplate;
    @Resource
    private RedissonClient redissonClient;

    @Override
    public PageUtils queryPage(Map<String, Object> params) {
        IPage<CategoryEntity> page = this.page(
                new Query<CategoryEntity>().getPage(params),
                new QueryWrapper<>()
        );

        return new PageUtils(page);
    }

    /**
     * 查出所有分类及其子分类，以树形结构组织
     */
    @Override
    public List<CategoryEntity> listWithTree() {
        //查出所分类
        List<CategoryEntity> categoryEntityList = baseMapper.selectList(null);
        //找出所有一级分类
        List<CategoryEntity> levelOneCategoryList = categoryEntityList.stream()
                .filter(categoryEntity -> categoryEntity.getParentId() == 0)
                .map((menu) -> {
                    menu.setChildren(getChildren(menu, categoryEntityList));
                    return menu;
                }).sorted((menu1, menu2) -> (menu1.getSort() == null ? 0 : menu1.getSort()) - (menu2.getSort() == null ? 0 : menu2.getSort()))
                .collect(Collectors.toList());

        return levelOneCategoryList;
    }

    /**
     * 批量删除没有子菜单的菜单，且菜单没有被其他地方引用
     */
    @Override
    public void deleteBatch(List<Long> asList) {
        //TODO 1、检查当前菜单是否被其他地方引用
        baseMapper.deleteBatchIds(asList);
    }

    /**
     * 修改，更新品牌分类关系表
     */
    @Override
    public void updateDetail(CategoryEntity category) {
        //更新本表
        baseMapper.updateById(category);
        //更新品牌分类关系表
        String name = category.getName();
        if (StringUtils.hasLength(name)) {
            brandCategoryRelationService.updateCategory(category.getId(), name);
        }

    }

    /**
     * 查询三级分类的完整路径
     *
     * @param categoryId 三级分类id
     * @return [1, 2, 3]
     */
    @Override
    public Long[] findCategoryPath(Long categoryId) {
        //也可以递归查找
        Long[] res = new Long[3];
        CategoryEntity categoryThree = baseMapper.selectById(categoryId);
        CategoryEntity categoryTwo = baseMapper.selectById(categoryThree.getParentId());
        res[0] = categoryTwo.getParentId();
        res[1] = categoryTwo.getId();
        res[2] = categoryId;
        return res;
    }


    //使用缓存
    @Cacheable(value = {"product"}, key = "#root.method.name")
    @Override
    public Map<String, List<Catalog2Vo>> getCatalogJson() {
        /**
         * 1、空结果缓存，解决缓存穿透
         * 2、设置随机过期时间，解决缓存雪崩
         * 3、加锁，解决缓存击穿
         */
        String catalogJSON = stringRedisTemplate.opsForValue().get("catalogJSON");
        if (StringUtils.hasLength(catalogJSON)) {
            //缓存中有数据
            return JSON.parseObject(catalogJSON, new TypeReference<>() {
            });
        } else {
            //缓存中没有数据
//            return getCatalogJsonLock();
            return getCatalogJsonRedissonClient();
        }


    }


    /**
     * 使用Redisson实现分布式锁
     */
    public Map<String, List<Catalog2Vo>> getCatalogJsonRedissonClient() {
        RLock lock = redissonClient.getLock("CatalogJson-Lock");
        lock.lock();
        Map<String, List<Catalog2Vo>> map;
        try {
            map = getMap();
        } finally {
            lock.unlock();
        }
        return map;
    }


    /**
     * 加synchronized实现本地锁
     */
    private synchronized Map<String, List<Catalog2Vo>> getCatalogJsonLocalLock() {
        return getMap();
    }

    /**
     * 使用分布式锁
     */
    private Map<String, List<Catalog2Vo>> getCatalogJsonLock() {
        String uuid = UUID.randomUUID().toString();
        Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("lock", uuid, 30, TimeUnit.SECONDS);
        if (lock) {
            //加锁成功，设置过期时间(原子性)
//            stringRedisTemplate.expire("lock", 30, TimeUnit.SECONDS);
            Map<String, List<Catalog2Vo>> map;
            try {
                map = getMap();
            } finally {
                //删除锁也必须是原子操作：lua脚本
                String lua = "if redis.call(\"get\",KEYS[1]) == ARGV[1]\n" +
                        "then\n" +
                        "    return redis.call(\"del\",KEYS[1])\n" +
                        "else\n" +
                        "    return 0\n" +
                        "end";
                stringRedisTemplate.execute(new DefaultRedisScript<>(lua, Long.class), Collections.singletonList("lock"), uuid);
            }


//            if (stringRedisTemplate.opsForValue().get("lock").equals(uuid)) {
//                //删除自己拥有的锁
//                stringRedisTemplate.delete("lock");
//            }
            return map;
        } else {
            //加锁失败，重试
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return getCatalogJsonLock();
        }


    }


    /**
     * 前台使用，从数据库中直接查出数据
     *
     * @return
     */
    //实验加分布式锁的提取的公共方法
    private Map<String, List<Catalog2Vo>> getMap() {
        String catalogJSON = stringRedisTemplate.opsForValue().get("catalogJSON");
        if (StringUtils.hasLength(catalogJSON))
            return JSON.parseObject(catalogJSON, new TypeReference<>() {
            });
        else {
            System.out.println("查询了数据库");


            //查询优化 仅查询一次数据库，其他数据经过遍历封装得到
            List<CategoryEntity> allCategoryEntityList = baseMapper.selectList(null);
            HashMap<String, List<Catalog2Vo>> map = new HashMap<>();
            //一级分类集合
            List<CategoryEntity> oneCategoryEntityList = allCategoryEntityList.stream().filter(item -> item.getLevel() == 1).collect(Collectors.toList());
            //二级分类集合
            List<CategoryEntity> twoCategoryEntityList = allCategoryEntityList.stream().filter(item -> item.getLevel() == 2).collect(Collectors.toList());
            //三级分类集合
            List<CategoryEntity> threeCategoryEntityList = allCategoryEntityList.stream().filter(item -> item.getLevel() == 3).collect(Collectors.toList());
            //封装
            Map<String, List<Catalog2Vo>> result = oneCategoryEntityList.stream()
                    .collect(Collectors.toMap((k) -> k.getId().toString()
                            , (v) -> twoCategoryEntityList.stream()
                                    .filter(twoCategory -> twoCategory.getParentId().equals(v.getId()))
                                    .map((twoCategory) -> {
                                        Catalog2Vo catalog2Vo = new Catalog2Vo();
                                        catalog2Vo.setCatalog1Id(twoCategory.getParentId().toString());
                                        catalog2Vo.setId(twoCategory.getId().toString());
                                        catalog2Vo.setName(twoCategory.getName());
                                        catalog2Vo.setCatalog3List(threeCategoryEntityList.stream()
                                                .filter(threeCategory -> threeCategory.getParentId().equals(twoCategory.getId()))
                                                .map((threeCategory) -> {
                                                    Catalog2Vo.catalog3Vo catalog3Vo = new Catalog2Vo.catalog3Vo();
                                                    catalog3Vo.setId(threeCategory.getId().toString());
                                                    catalog3Vo.setName(threeCategory.getName());
                                                    catalog3Vo.setCatalog2Id(v.getId().toString());
                                                    return catalog3Vo;
                                                }).collect(Collectors.toList()));
                                        return catalog2Vo;
                                    }).collect(Collectors.toList())));
            stringRedisTemplate.opsForValue().set("catalogJSON", JSON.toJSONString(result), 1, TimeUnit.DAYS);
            return result;

        }
    }

    //递归找寻每个分类的子分类
    private List<CategoryEntity> getChildren(CategoryEntity root, List<CategoryEntity> all) {
        return all.stream()
                .filter(categoryEntity -> categoryEntity.getParentId().equals(root.getId()))
                .map((categoryEntity) ->
                {
                    categoryEntity.setChildren(getChildren(categoryEntity, all));
                    return categoryEntity;
                })
                .sorted((menu1, menu2) -> (menu1.getSort() == null ? 0 : menu1.getSort()) - (menu2.getSort() == null ? 0 : menu2.getSort()))
                .collect(Collectors.toList());
    }

}